import os
import uuid
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import START, MessagesState, StateGraph
from langchain_openai import ChatOpenAI
from langchain_core.messages import AIMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# import streamlit as st
from munch import Munch
from services.semantic_search import SemanticSearch
# modules by myself
from data_interface.getEventsFromLocal import excel_to_dicts
from config.parseConfig import getConfKey
# from models.user import User

# 配置环境变量
def InitEnviroment():
    os.environ["OPENAI_API_KEY"] = getConfKey("config/conf.txt", "OPENAI_API_KEY")
    os.environ["OPENAI_API_BASE"] = getConfKey("config/conf.txt", "OPENAI_API_BASE")
    os.environ["LANGCHAIN_TRACING_V2"] = "true"
    os.environ["LANGSMITH_TRACING"] = "true"
    os.environ["LANGSMITH_API_KEY"] = getConfKey("config/conf.txt", "LANGSMITH_API_KEY")
    os.environ["LANGSMITH_ENDPOINT"] = getConfKey("config/conf.txt", "LANGSMITH_ENDPOINT")
    os.environ["LANGCHAIN_PROJECT"] = getConfKey("config/conf.txt", "LANGCHAIN_PROJECT")   

def ReadLocalData():
    # 获取赛事信息，为了方便从本地CSV文件中读取预先准备的赛事信息
    # infor=str(getEventsInfo("data/eventsInfo.csv"))
    # price = str(getEventsPrice("data/eventsPrice.csv"))
    # tickes = str(getEventsTikets("data/remainingTickets.csv"))
    events = excel_to_dicts("data/eventsInfo.xlsx")
    prices = excel_to_dicts("data/eventsPrice.xlsx")
    tickets = excel_to_dicts("data/remainingTickets.xlsx")
    return events, prices, tickets

def InitPrompt():   
    # # 自定义的 Prompt
    # return ChatPromptTemplate.from_messages(
    #     [
    #         (
    #             "system",
    #             """1. Your role: You are a professional who sells tickets for various sports events. Your name is SportsHub. You need to introduce yourself and ask the customer to introduce their name. You need to tell the customer the services you can provide, including providing event information, ticket prices, and ticket purchases.
    #                 2. The information you have comes from three tables: event information and event ticket price information. Event information comes from the event information table: [eventsInfo]:{eventsInfo}. Ticket price information comes from the event price table: [eventsPrice]:{eventsPrice}. The remaining tickets for the event come from the remaining ticket table: [remainingTickets]:{remainingTickets}. The EventID items in the three tables are unique and identical, and can be used to associate the three tables.
    #                 3. What you need to do:
    #                     3.1 Please output all the information that needs to be displayed to the customer in a table format. When the customer needs to select a specific event based on your prompt information, please ask the customer to enter the event ID, which is the EventID in the table.
    #                     3.2 You need to find the events that the customer is interested in from the event information table through the dialogue, and then merge the rows with the same EventID in the event information table and the event price table and show them to the customer. 
    #                     3.3 You need to ask the customer to confirm the EventID number, seat class and quantity of the event he is interested in. 
    #                     3.4 You need to ask the customer to confirm his ticket information, quantity, unit price and total price. 
    #                     3.5 **Data verification**: After the customer confirms the ticket class and quantity, be sure to check the remaining ticket table (remainingTickets) to carefully check whether the user's ticket class and the number of tickets of the corresponding class in the remaining ticket table meet the customer's needs. 
    #                     3.6 If it does not meet the requirements, prompt the customer to reconsider the ticket class or quantity purchased and re-count. If the event ticket class and quantity determined by the customer can meet the requirements, don't forget to modify the remaining tickets in the remaining ticket table of the event you have at the end of the transaction. 
    #                     3.7 You need to ask the user to confirm the user's name and other key information that can be used to purchase tickets. 
    #                     3.8 You need to ask the user to enter his valid email address. A valid email address means that the email address should contain the @ character and a part that looks like a domain name, and tell the customer that you will send the ticket information to the customer's email address. 
    #                     3.9 Finally, when outputting the complete order information, please start the information with "xyz Your order information is as follows:"
    #                 """
    #         ),
    #         MessagesPlaceholder(variable_name="messages"),
    #     ]
    # )
    # 自定义的 Prompt
    return ChatPromptTemplate.from_messages(
        [
            (
                "system",
                """ 1. Your role: You are a professional who sells tickets for various sports events. Your name is SportsHub. You need to introduce yourself and say hello to {username} at first. You need to tell {username} the services you can provide, including providing event information, ticket prices, and ticket purchases.
                    2. The information you have comes from three tables: event information and event ticket price information. Event information comes from the event information table: [eventsInfo]:{eventsInfo}. Ticket price information comes from the event price table: [eventsPrice]:{eventsPrice}. The remaining tickets for the event come from the remaining ticket table: [remainingTickets]:{remainingTickets}. The EventID items in the three tables are unique and identical, and can be used to associate the three tables.
                    3. What you need to do:
                        3.1 Please output all the information that needs to be displayed to the customer in a table format. When the customer needs to select a specific event based on your prompt information, please ask the customer to enter the event ID, which is the EventID in the table.
                        3.2 You need to find the events that the customer is interested in from the event information table through the dialogue, and then merge the rows with the same EventID in the event information table and the event price table and show them to the customer. 
                        3.3 You need to ask the customer to confirm the EventID number, seat class and quantity of the event he is interested in. 
                        3.4 You need to ask the customer to confirm his ticket information, quantity, unit price and total price. 
                        3.5 **Data verification**: After the customer confirms the ticket class and quantity, be sure to check the remaining ticket table (remainingTickets) to carefully check whether the user's ticket class and the number of tickets of the corresponding class in the remaining ticket table meet the customer's needs. 
                        3.6 If it does not meet the requirements, prompt the customer to reconsider the ticket class or quantity purchased and re-count. If the event ticket class and quantity determined by the customer can meet the requirements, don't forget to modify the remaining tickets in the remaining ticket table of the event you have at the end of the transaction. 
                        3.7 You need to ask the user to confirm the user's name{username} and email address{email}. 
                        3.8 Finally, collectted all order information, say: "{username}, your order is ready ~!"
                    4. After fianl check just output JSON structure only:
                        ```json
                        {{
                            "order_data": {{
                                "event_id": "",
                                "seat_class_A": 0,
                                "seat_class_B": 0,
                                "seat_class_C": 0,
                                "user_name": "{username}",
                                "user_email": "{email}",
                                "total_price": 0.0,
                                }},
                            "completed": true/false, // must be true if all data has been collected and verified
                            "next_required_field": "[field name]"
                        }}
                        ```
                    """
            ),
            MessagesPlaceholder(variable_name="messages"),
        ]
    )
    
def InitModel(prompt_template, events, prices, tickets, username, email):
    # 初始化模型
    model = ChatOpenAI(model="gpt-4o-mini")
    # 定义一个新的图
    workflow = StateGraph(state_schema=MessagesState)
    
    # 定义调用模型的函数
    def call_model(state: MessagesState):
        # higlight-start
        prompt = prompt_template.invoke({
            "messages": state['messages'],# 提取实际的消息列表
            "username": username, # 显式传递模板变量
            "email": email, # 显式传递模板变量
            "eventsInfo": events,  # 显式传递模板变量
            "eventsPrice": prices,  # 显式传递模板变量
            "remainingTickets": tickets  # 显式传递模板变量
        })
        # higlight-end
        response = model.invoke(prompt)
        return {"messages":response}
    
    # 定义图中的节点
    workflow.add_edge(START, "model")
    workflow.add_node("model", call_model)

    # 添加内存
    memory = MemorySaver()
    app = workflow.compile(checkpointer=memory)
    
    # 设置会话id
    config = {"configurable": {"thread_id": str(uuid.uuid4())}}
    
    return app,config

def initAgent(session_state):
    # 只初始化一次
    if session_state.agent is None:
        # 初始化环境变量
        InitEnviroment()
        # 初始化数据
        events, prices, tickets = ReadLocalData()   
        
        # 初始化所有需要的数据结构
        session_state.events_data = Munch({
            'events': events,
            'prices': prices,
            'tickets': tickets
        })
        
        session_state.order_data = {
            "event_id": "",
            "seat_class_A": 0,
            "seat_class_B": 0,
            "seat_class_C": 0,
            "user_name": session_state.user.getName(),
            "user_email": session_state.user.getEmail(),
            "total_price": 0.0,
            "completed": False,
        }
        
        prompt_template=InitPrompt()
        
        agent,config=InitModel(prompt_template, events, prices, tickets, 
                             session_state.user.getName(), session_state.user.getEmail())
        session_state.agent = agent
        session_state.config = config
        print("agent and config initialized")

def initSearchEngine(session_state):
    # 初始化语义搜索
    if session_state.search_engine is None:
        if not hasattr(session_state, 'events_data'):
            events, prices, tickets = ReadLocalData()
            session_state.events_data = Munch({
                'events': events,
                'prices': prices,
                'tickets': tickets
            })
        
        session_state.search_engine = SemanticSearch()
        
        event_texts = [
            f"{e['Event Name']}|{e['Types of Sports Events']}|{e['Teams']}"
            for e in session_state.events_data['events']
        ]
        
        session_state.search_engine.build_index(event_texts)
        print("search engine initialized")
        
def SendMessage(app,config,input_messages):
    # 自定义消息输出格式
    result = app.invoke({"messages": input_messages}, config)
    if result["messages"]:
        last_message = result["messages"][-1]
        if isinstance(last_message, AIMessage):
            # 使用自定义标题替换默认的 "Ai Message"
            return("SpotsHub: " + last_message.content + "\n")